package com.github.zyf.ioaj.export;

import com.github.zyf.ioaj.algorithm.AbstractAlgorithm;
import com.github.zyf.ioaj.annotation.internal.Algorithm;
import com.github.zyf.ioaj.annotation.internal.InputParam;
import com.github.zyf.ioaj.constant.FileType;
import com.github.zyf.ioaj.exception.AlgorithmClassParserException;
import com.github.zyf.ioaj.exception.constant.AlgorithmClassParserExceptionType;

import java.lang.reflect.Field;

/**
 * <h1>跟踪记录</h1>
 * <p>可以使用{@link AbstractAlgorithm#report(String, String, String, FileType)}方法加载算法跟踪记录，边计算边记录，计算完成后所返回的对象便是当前类</p>
 * <p>
 * 跟踪报告将记录每一次迭代算法所输出的信息，每一次迭代时会解析添加了{@link com.github.zyf.ioaj.annotation.internal.TempParam}注解的字段，运算器计算完成时将记录如下三个字段内容
 * <ul>
 *     <li>{@link com.github.zyf.ioaj.annotation.internal.BasicParam}</li>
 *     <li>{@link com.github.zyf.ioaj.annotation.internal.InputParam}</li>
 *     <li>{@link com.github.zyf.ioaj.annotation.internal.OutputParam}</li>
 * </ul>
 * 由于算法的迭代次数一般非常多，因此非必要不建议使用该方法，一来消耗资源，二来导出的内容较大
 *
 * @author zyf
 */
public class Tracker extends AbstractExport {

    private ContentBuilder contentBuilder;

    private boolean isStarted;

    private boolean isEnd;

    public Tracker(AbstractAlgorithm algorithm, String author, String message, String title, FileType fileType) {
        super(algorithm, author, message, title, fileType);
        this.isStarted = false;
    }

    public ContentBuilder getContentBuilder() {
        return contentBuilder;
    }

    public void startTracker() {
        if (isEnd) return;
        // 在开始前预处理（预检查）
        boolean isAnnotated = checkClassForTemp();
        if (!isAnnotated) {
            throw new AlgorithmClassParserException(AlgorithmClassParserExceptionType.ALGORITHM_PARAM_NOT_ANNOTATED);
        }
        switch (getFileType()) {
            case JSON:
                contentBuilder = new JsonContentBuilder(getTitle(), getAuthor(), getMessage());
                contentBuilder.appendNewArrayHeader("tracker");
                this.isStarted = true;
                break;
            case TXT:
                contentBuilder = new TxtContentBuilder(getTitle(), getAuthor(), getMessage());
                this.isStarted = true;
                contentBuilder.appendNewArrayHeader("tracker");
                break;
        }
    }

    public void trackOne(int d) {
        if (isEnd) return;
        if (isStarted) {
            buildOneNodeForContent(contentBuilder, d);
        }
    }

    public void endTracker() {
        if (isEnd) return;
        if (isStarted) {
            contentBuilder.appendNewArrayEnd("tracker");
            this.isEnd = true;
        }
    }

    /**
     * 构建加载器内容
     *
     * @param contentBuilder 指定内容加载器
     * @return 加载器内容
     */
    private String buildContent(ContentBuilder contentBuilder) {
        // 获取算法所在类
        Class<?> clazz = getAlgorithm().getClass();
        // 检验是否标有Algorithm注解，否则抛出异常
        if (!clazz.isAnnotationPresent(Algorithm.class)) {
            throw new AlgorithmClassParserException(AlgorithmClassParserExceptionType.ALGORITHM_NOT_ANNOTATED);
        }
        // 从子类到父类层层检验是否有字段属于算法参数（至少有一个即可），否则抛出异常
        boolean isAnnotated = false;
        while (clazz != null) {
            // 扫描类
            boolean b = scanClass(clazz, getAlgorithm(), contentBuilder);
            if (b) isAnnotated = true;
            // 查找父类
            clazz = clazz.getSuperclass();
        }
        if (!isAnnotated)
            throw new AlgorithmClassParserException(AlgorithmClassParserExceptionType.ALGORITHM_PARAM_NOT_ANNOTATED);
        else {
            contentBuilder.complete(getAlgorithm().getClass());
            setLoaded();
        }
        return contentBuilder.toString();
    }

    /**
     * 扫描类（包括其父类），用以解析相关数据
     *
     * @param algorithm      需要扫描的类
     * @param contentBuilder 内容加载器
     */
    private boolean scanClass(Class<?> clazz, AbstractAlgorithm algorithm, ContentBuilder contentBuilder) {
        // 用以检查字段是否被注解，如果扫描了所有字段，一个都没注解，最后会抛出异常
        boolean isAnnotated = false;
        // 遍历所有字段
        for (Field field : clazz.getDeclaredFields()) {
            // 允许字段在访问时可以被检查
            field.setAccessible(true);
            // 判断字段是否添加了对应的注解
            boolean b = scanBasicParam(algorithm, field, contentBuilder)
                    || scanInputParam(algorithm, field, contentBuilder)
                    || scanOutputParam(algorithm, field, contentBuilder);
            if (b) isAnnotated = true;
        }
        return isAnnotated;
    }

    /**
     * 构建加载器内容
     *
     * @param contentBuilder 指定内容加载器
     */
    private void buildOneNodeForContent(ContentBuilder contentBuilder, int d) {
        Class<?> clazz = getAlgorithm().getClass();
        String nodename = "iter_" + d;
        contentBuilder.appendNewNodeHeader(nodename, "第" + d + "次迭代");
        while (clazz != null) {
            boolean b = scanClassForTemp(clazz, getAlgorithm(), contentBuilder);
            clazz = clazz.getSuperclass();
        }
        contentBuilder.appendNewNodeEnd(nodename);
        setLoaded();
    }

    private boolean checkClassForTemp() {
        Class<?> clazz = getAlgorithm().getClass();
        if (!clazz.isAnnotationPresent(Algorithm.class)) {
            throw new AlgorithmClassParserException(AlgorithmClassParserExceptionType.ALGORITHM_NOT_ANNOTATED);
        }
        boolean isAnnotated = false;
        // 预处理
        while (clazz != null) {
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(InputParam.class)) {
                    isAnnotated = true;
                    break;
                }
            }
            clazz = clazz.getSuperclass();
        }
        return isAnnotated;
    }

    /**
     * 扫描类（包括其父类），用以解析相关数据
     *
     * @param algorithm      需要扫描的类
     * @param contentBuilder 内容加载器
     */
    private boolean scanClassForTemp(Class<?> clazz, AbstractAlgorithm algorithm, ContentBuilder contentBuilder) {
        // 用以检查字段是否被注解，如果扫描了所有字段，一个都没注解，最后会抛出异常
        boolean isAnnotated = false;
        // 遍历所有字段
        for (Field field : clazz.getDeclaredFields()) {
            // 允许字段在访问时可以被检查
            field.setAccessible(true);
            // 判断字段是否添加了对应的注解
            boolean b = scanTempParam(algorithm, field, contentBuilder);
            if (b) isAnnotated = true;
        }
        return isAnnotated;
    }

    /**
     * 将报告输出为字符串
     *
     * @return {@link #getFileType()} 格式字符串
     */
    @Override
    public String export() {
        if (isEnd) return buildContent(contentBuilder);
        else return "{\"state\": \"未加载完成\"}";
    }
}
